﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.IO;
using System.Diagnostics;
using System.Threading;
using System.Windows.Forms;

using System.Text.RegularExpressions;

using TranscoderSharp.Coders;
using TranscoderSharp;

namespace TranscoderSharp
{
    public class Transcoder : List<AudioFile>
    {
        // should be in a config file somwhere
        private static string[] m_FileTypes = { "*.mp3", "*.ogg", "*.flac" };

        static string appMP3Val = Path.Combine(Application.StartupPath, "mp3val.exe");
        static string appMP3Gain = Path.Combine(Application.StartupPath, "mp3gain.exe");

        public event EventHandler TranscodingStarted;
        public delegate void AudioFileProcessedEventHandler(object sender, TranscoderEventArgs e);
        public event AudioFileProcessedEventHandler AudioFileProcessed;
        public event EventHandler TranscodingFinished;

        public Transcoder()
        {

        }

        

        /*
        public static List<AudioFile> GetAudioFiles(string Folder)
        {
            List<AudioFile> AudioFiles = new List<AudioFile>();

            

            foreach (string FileType in m_FileTypes)
            {
                string[] Files = Directory.GetFiles(Folder, FileType, SearchOption.AllDirectories);
                AudioFiles.AddRange(Files.Select(p => new AudioFile(p)));
            }

            //AudioFiles.Exists();

            return AudioFiles;
        }
         */

        public void LoadAudioFiles(string Folder)
        {
            foreach (string FileType in m_FileTypes)
            {
                string[] Files = Directory.GetFiles(Folder, FileType, SearchOption.AllDirectories);
                this.AddRange(Files.Select(p => new AudioFile(p)));
                //this.AddRange(Files.Select(p => new AudioFile(p)).Where(p => p.TagLibFile != null));
            }

            //this.RemoveAll(p => p.TagLibFile == null);

            //this.RemoveAll(p => p.SizeMB > 10);
        }

        public void Transcode()
        {
            if (TranscodingStarted != null)
                TranscodingStarted(this, new EventArgs());

            foreach (AudioFile af in this)
            {
                TranscodeFile(af as object);

                TranscoderEventArgs tea = new TranscoderEventArgs(af);
                if (AudioFileProcessed != null)
                    AudioFileProcessed(this, tea);
            }

            if (TranscodingFinished != null)
                TranscodingFinished(this, new EventArgs());
        }

        public void TranscodeFile(object af)
        {
            try
            {
                if (!(af is AudioFile))
                    throw new Exception();

                if (String.IsNullOrEmpty(ConfigObject.OutputDirectory))
                    throw new Exception();

                AudioFile AudioFile = af as AudioFile;

                Stopwatch stopWatch = new Stopwatch();
                CoderInterface Decoder = Transcoder.GetCoder(AudioFile);
                Lame Encoder = new Lame();

                string OutputFilePath = AudioFile.GetOutputFilePath(ConfigObject.OutputDirectory, 
                    Lame.Extension, ConfigObject.OutputFormatPath);

                // if output file exists check if we want to skip encoding
                if (File.Exists(OutputFilePath) && !ConfigObject.OverwriteExisting)
                {
                    AudioFile.Status = AudioFile.EncodingStatus.Skipped;
                    return;
                }

                stopWatch.Start();

                //decode to wav
                string WaveFilePath = Decoder.DecodeToWav(AudioFile);

                //encode to output (mp3)

                (Encoder as CoderInterface).EncodeWav(AudioFile, WaveFilePath, ConfigObject);

                // delete wave file
                File.Delete(WaveFilePath);

                (AudioFile).CopyTagToFile(OutputFilePath);

                (AudioFile).EncodingTime = stopWatch.Elapsed;

            }
            catch (Exception ex)
            {
                (af as AudioFile).Status = AudioFile.EncodingStatus.Error;
                try
                {

                    System.Net.Mail.SmtpClient smtpClient = new System.Net.Mail.SmtpClient("relay.clara.net");
                    //smtpClient.Port = 465;
                    //smtpClient.EnableSsl = true;
                    smtpClient.Credentials = new System.Net.NetworkCredential("eps@epscylonb.com", "oasis12");
                    smtpClient.Send("Encledus@epscylonb.com",
                        "andy.burchill@gmail.com",
                        "[EXCEPTION] " + (af as AudioFile).Title,
                        (af as AudioFile).ToString() + "\r\n" + ex.ToString() + "\r\n" +
                        (af as AudioFile).FilePath + "\r\n" + (af as AudioFile).GetMp3ValidationResult());
                }

                catch
                {

                }


            }

            /*
            try
            {
                LameEncoder.EncodeAudioFile(af as AudioFile);
            }
            catch (Exception ex)
            {
                (af as AudioFile).Status = AudioFile.EncodingStatus.Error;
                try
                {

                    System.Net.Mail.SmtpClient smtpClient = new System.Net.Mail.SmtpClient("relay.clara.net");
                    //smtpClient.Port = 465;
                    //smtpClient.EnableSsl = true;
                    smtpClient.Credentials = new System.Net.NetworkCredential("eps@epscylonb.com", "oasis12");
                    smtpClient.Send("Encledus@epscylonb.com",
                        "andy.burchill@gmail.com",
                        "[EXCEPTION] " + (af as AudioFile).Title,
                        (af as AudioFile).ToString() + "\r\n" + ex.ToString() + "\r\n" +
                        (af as AudioFile).FilePath + "\r\n" + (af as AudioFile).GetMp3ValidationResult());
                }

                catch
                {

                }

                
            }

            (af as AudioFile).DeleteTempFile();
             */
        }

        private static CoderInterface GetCoder(AudioFile af)
        {
            switch (af.FileInfo.Extension.ToLower())
            {
                case ".mp3":
                    return new Lame();
                case ".flac":
                    return new Flac();
                default:
                    throw new Exception("No Encoder/Decoder found for filetype " + af.FileInfo.Extension);
            }
        }

        public static void TranscodeFile(AudioFile af)
        {
            //LameEncoder.EncodeAudioFile(af);
        }



        /// <summary>
        /// Apply track gain
        /// </summary>
        /// <param name="FilePath"></param>
        public static void ApplyMp3Gain(string FilePath, int TimeOut)
        {
            CallExternalProgramWithOutput(appMP3Gain, "/r /p /q \"" + FilePath + "\"", TimeOut);
        }

        public ConfigObject ConfigObject { get; set; }

        public int Processed
        {
            get
            {
                return this.Where(p => p.Status != AudioFile.EncodingStatus.Unencoded).Count();
            }
        }

        public int Encoded
        {
            get
            {
                return this.Where(p => p.Status == AudioFile.EncodingStatus.Encoded).Count();
            }
        }

        public int Skipped
        {
            get
            {
                return this.Where(p => p.Status == AudioFile.EncodingStatus.Skipped).Count();
            }
        }

        public int Unencoded
        {
            get
            {
                return this.Where(p => p.Status == AudioFile.EncodingStatus.Unencoded).Count();
            }
        }

        public int Errors
        {
            get
            {
                return this.Where(p => p.Status == AudioFile.EncodingStatus.Error).Count();
            }
        }

        // time

        //public TimeSpan AverageEncodingTime
        //{
        //    get
        //    {
        //        long ticks = this.Where(p => p.Status == AudioFile.EncodingStatus.Encoded).Select(p => p.EncodingTime).Sum(p => p.Ticks);
        //        ticks = ticks / this.Encoded;
        //        return new TimeSpan(ticks);
        //    }
        //}      

        //public TimeSpan TotalTime
        //{
        //    get
        //    {
        //        long ticks = this.Select(p => p.EncodingTime).Sum(p => p.Ticks);
        //        return new TimeSpan(ticks);
        //    }
        //}

        //public TimeSpan RemainingTime
        //{
        //    get
        //    {
        //        long ticks = AverageEncodingTime.Ticks * this.Unencoded;
        //        return new TimeSpan(ticks);
        //    }
        //}

        public long TotalBytesEncoded
        {
            get
            {
                return this.Where(p => p.Status == AudioFile.EncodingStatus.Encoded).Select(p => p.Size).Sum();
            }
        }

        public long TotalBytesUnEncoded
        {
            get
            {
                return this.Where(p => p.Status == AudioFile.EncodingStatus.Unencoded).Select(p => p.Size).Sum();
            }
        }

        public TimeSpan AverageTimePerByte
        {
            get
            {
                long ticks = this.Where(p => p.Status == AudioFile.EncodingStatus.Encoded).Select(p => p.EncodingTime).Sum(p => p.Ticks);
                return new TimeSpan(ticks / TotalBytesEncoded);
            }
        }

        public TimeSpan RemainingTime
        {
            get
            {
                long ticks = AverageTimePerByte.Ticks * this.TotalBytesUnEncoded;
                return new TimeSpan(ticks);
            }
        }

        // call external programs

        public static int CallExternalProgram(string App, string Arguments, int TimeOut)
        {
            ProcessStartInfo psi = new ProcessStartInfo(App, Arguments);
            psi.CreateNoWindow = true;
            psi.WindowStyle = ProcessWindowStyle.Hidden;
            psi.UseShellExecute = false;

            Process p = Process.Start(psi);

            p.WaitForExit(TimeOut * 1000);

            while (!p.HasExited)
            {
                if (p.StartTime.Add(new TimeSpan(0, TimeOut, 0)) < DateTime.Now)
                {
                    p.Kill();
                    p.Close();
                    return -1;
                }
                Thread.Sleep(5000);
                //Application.DoEvents();
            }

            try
            {
                int i = p.ExitCode;
                p.Kill();
                p.Close();
                return i;
            }
            catch { }

            return p.ExitCode;
        }

        public static string CallExternalProgramWithOutput(string App, string Arguments, int TimeOut)
        {
            ProcessStartInfo psi = new ProcessStartInfo(App, Arguments);
            psi.CreateNoWindow = true;
            psi.WindowStyle = ProcessWindowStyle.Hidden;
            psi.UseShellExecute = false;
            psi.RedirectStandardError = true;
            psi.RedirectStandardOutput = true;

            Process p = Process.Start(psi);

            p.WaitForExit(TimeOut * 1000);

            while (!p.HasExited)
            {
                if (p.StartTime.Add(new TimeSpan(0, TimeOut, 0)) < DateTime.Now)
                {
                    p.Kill();
                    p.Close();
                    throw new Exception(App + " with arguments " + Arguments + " timed out.");
                }
                Thread.Sleep(5000);
                //Application.DoEvents();
            }

            string output = p.StandardOutput.ReadToEnd();
            string error = p.StandardError.ReadToEnd();

            return output + "\r\n" + error;
        }


        

        /// <summary>
        /// Lame (or windows cli) can't handle certain characters so we "clean" 
        /// the output file string by replacing those characters with an underscore.
        /// </summary>
        /// <param name="StringObject"></param>
        /// <returns></returns>
        public static string CleanString(string StringObject)
        {
            StringObject = Encoding.ASCII.GetString(Encoding.ASCII.GetBytes(StringObject));

            string rgPattern = @"[\\\/:\*\?""'<>|]";
            //string rgPattern = new string(Path.GetInvalidFileNameChars());
            Regex objRegEx = new Regex(rgPattern);
            return objRegEx.Replace(StringObject, "");

        }

        internal static string GetTempFileName(string extension)
        {
            string path = System.IO.Path.Combine(System.IO.Path.GetTempPath(), Guid.NewGuid() + extension);

            if (File.Exists(path))
                File.Delete(path);

            return path;
        }

        public bool IsTrackFromVariousArtistsAlbum(AudioFile af)
        {
            //var temp = this.Where(p => p.Album == af.Album).Select(s => s.Artist).Distinct();
            //int distinct = this.Where(p => p.Album == af.Album).Select(s => s.Artist).Distinct().Count();

            if (String.IsNullOrEmpty(af.Album))
                return false;

            if (af.Tracknumber == 0)
                return false;

            IEnumerable<AudioFile> albumtracks = this.Where(p => p.Album == af.Album);
            //IEnumerable<AudioFile> albumtracks = this.TakeWhile(p => p.Album == af.Album);
            IEnumerable<string> artists = albumtracks.Select(a => a.Artist).Distinct();

            return artists.Skip(1).Any();

            return this.Where(p => p.Album == af.Album).Select(s => s.Artist).Distinct().Take(1).Any();
        }

        //public static bool OutputFileExists(AudioFile AudioFileObject)
        //{
        //    string FullOutputFilePath = Transcoder.GetOutputFilePath(AudioFileObject);
        //    return File.Exists(FullOutputFilePath);

        //}
    }
}
